using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.VisualStudio.TestPlatform.Common;
using Newtonsoft.Json;

namespace RDJTProtocolServer
{
    public class CategoriesController
    {
        /**
         * The Default path separator.
         */
        private const string PathSeparator = "/";

        /**
         * The Default API path prefix.
         */
        private const string ApiPrefix = "api";

        /**
         * The Default name of the storage map.
         */
        private const string StorageName = "categories";

        /**
         * The request path for a category request.
         */
        private const string CategoryRequestPath = PathSeparator + ApiPrefix + PathSeparator + StorageName;

        /**
         * Map containing category data.
         */
        private Dictionary<int, string> _categories = new Dictionary<int, string>()
        {
            {1, "Beverages"},
            {2, "Condiments"},
            {3, "Confections"},
        };

        /**
         * Execute the RDJTP request using the response object for output.
         */
        public void HandleRequest(RdjtpRequest request, RdjtpResponse response)
        {
            switch (request.Method)
            {
                case "read":
                    Read(request, response);
                    break;
                case "create":
                    Create(request, response);
                    break;
                case "update":
                    Update(request, response);
                    break;
                case "delete":
                    Delete(request, response);
                    break;
                default:
                    response.Status = RdjtpStatus.GetText(RdjtpStatus.Error);
                    break;
            }
        }

        /**
         * Executes the Read method of RDJTP protocol.
         */
        private void Read(RdjtpRequest request, RdjtpResponse response)
        {
            // Checking if client request all categories.
            if (request.Path.Equals(CategoryRequestPath))
            {
                this.toJson();
                response.Status = RdjtpStatus.GetText(RdjtpStatus.Ok);
            }
            // Checking if client requested a specific category.
            else if (request.Path.Contains(CategoryRequestPath + PathSeparator))
            {
                string untestedCid = request.Path.Replace(CategoryRequestPath + PathSeparator, String.Empty);

                // Checking if the cid provided contains something else besides digits.
                if (!untestedCid.All(char.IsDigit))
                {
                    response.Status = RdjtpStatus.GetText(RdjtpStatus.BadRequest);
                }
                // Cid is valid, proceeding with handling request.
                else
                {
                    int cid = Int32.Parse(untestedCid);

                    if (!_categories.ContainsKey(cid))
                    {
                        response.Status = RdjtpStatus.GetText(RdjtpStatus.NotFound);
                        return;
                    }

                    // Requested resource exists, serve the client.
                    this.toJson(cid);
                    response.Status = RdjtpStatus.GetText(RdjtpStatus.Ok);
                }
            }
            // The request is invalid at this point.
            else
            {
                response.Status = RdjtpStatus.GetText(RdjtpStatus.BadRequest);
            }
        }

        /**
         * Executes the Create method of RDJTP protocol.
         */
        private void Create(RdjtpRequest request, RdjtpResponse response)
        {
            // If request has vaid path and body proceed.
            if (request.Path.Equals(CategoryRequestPath)
                && request.Body != null
                && !request.Body.Equals(String.Empty))
            {
                // Read the Category from body.
                var category = JsonConvert.DeserializeObject<Category>(request.Body);
                // Build a new index for the new element and add it to the map.
                var newIndex = _categories.Count + 1;
                _categories.Add(newIndex, category.Name);

                // Serve the client.
                this.toJson(newIndex);
                response.Status = RdjtpStatus.GetText(RdjtpStatus.Created);
            }
            // The request is invalid.
            else
            {
                response.Status = RdjtpStatus.GetText(RdjtpStatus.BadRequest);
            }
        }

        /**
         * Executes the Update method of RDJTP protocol.
         */
        private void Update(RdjtpRequest request, RdjtpResponse response)
        {
            // If the path is valid proceed.
            if (request.Path.Contains(CategoryRequestPath + PathSeparator))
            {
                // Obtain the category id from the request path.
                int cid = Int32.Parse(request.Path.Replace(CategoryRequestPath + PathSeparator, String.Empty));

                // If there is no category with the provided id, send status and stop execution.
                if (!_categories.ContainsKey(cid))
                {
                    response.Status = RdjtpStatus.GetText(RdjtpStatus.NotFound);
                    return;
                }

                // Get the updated category from body.
                Category category = JsonConvert.DeserializeObject<Category>(request.Body);

                // Perform the update.
                _categories.Remove(cid);
                _categories.Add(cid, category.Name);

                // Serve the client.
                this.toJson(cid);
                response.Status = RdjtpStatus.GetText(RdjtpStatus.Updated);
            }
            // Requests with invalid path.
            else
            {
                response.Status = RdjtpStatus.GetText(RdjtpStatus.BadRequest);
            }
        }

        /**
         * Executes the Delete method of RDJTP protocol.
         */
        private void Delete(RdjtpRequest request, RdjtpResponse response)
        {
            // If the path is valid proceed.
            if (request.Path.Contains(CategoryRequestPath + PathSeparator))
            {
                // Retrieve and parse the category id.
                int cid = Int32.Parse(request.Path.Replace(CategoryRequestPath + PathSeparator, String.Empty));

                // Try to perform delete.
                if (_categories.Remove(cid))
                {
                    response.Status = RdjtpStatus.GetText(RdjtpStatus.Ok);
                }
                // Delete was not successful (most likely the requested id does not exist).
                else
                {
                    response.Status = RdjtpStatus.GetText(RdjtpStatus.NotFound);
                }
            }
            // Path is not valid.
            else
            {
                response.Status = RdjtpStatus.GetText(RdjtpStatus.BadRequest);
            }
        }

        /**
         * Helper function to convert the entire categories map to JSON.
         */
        private string toJson()
        {
            // Convert dictionary to List just before serializing it.
            var list = new List<Category>();
            foreach (var key in _categories.Keys)
            {
                var category = new Category(key, _categories.GetValueOrDefault(key));
                list.Add(category);
            }

            // Serialize the newly created list of Category objects.
            return JsonConvert.SerializeObject(list);
        }

        /**
         * Helper function to convert the category with provided cid to JSON.
         */
        private string toJson(int cid)
        {
            var category = new Category(cid, _categories.GetValueOrDefault(cid));
            return JsonConvert.SerializeObject(category);
        }
    }
}